/*

 Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

 Contact:
 SYSTAP, LLC DBA Blazegraph
 2501 Calvert ST NW #106
 Washington, DC 20008
 licenses@blazegraph.com

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; version 2 of the License.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */
/*
 * Created on Jan 17, 2008
 */

package com.bigdata.bfs;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.bigdata.bfs.BigdataFileSystem;
import com.bigdata.bfs.Document;
import com.bigdata.bfs.DocumentImpl;
import com.bigdata.bfs.FileMetadataSchema;
import com.bigdata.bfs.RepositoryDocumentImpl;
import com.bigdata.journal.TimestampUtility;
import com.bigdata.sparse.ITPS;
import com.bigdata.sparse.ITPV;

/**
 * Test operations on the file metadata index for the {@link BigdataFileSystem}.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public class TestFileMetadataIndex extends AbstractRepositoryTestCase {

    /**
     * 
     */
    public TestFileMetadataIndex() {
    }

    /**
     * @param arg0
     */
    public TestFileMetadataIndex(String arg0) {
        super(arg0);
    }

    /**
     * Create a binary file and verifies its metadata and content.
     * 
     * @throws IOException
     */
    public void test_create_binary01() throws IOException {

        final String id = "test";

        final String mimeType = "application/octet-stream";

        final byte[] content = new byte[] { 1, 2, 3, 4, 5, 6 };

        DocumentImpl doc = new DocumentImpl();

        doc.setId(id);

        doc.setContentType(mimeType);

        doc.setProperty("foo", "bar");

        doc.copyStream(content);

        repo.create(doc);

        Document actual = repo.read(id);

        assertEquals("version", 0, ((RepositoryDocumentImpl) actual)
                .getVersion());

        assertEquals("user property", "bar", actual.getProperty("foo"));

        assertEquals("Content-Type", mimeType, actual.getContentType());

        assertEquals("content", content, read(actual.getInputStream()));

    }

    /**
     * Create a text file and verify its metadata and content.
     * 
     * @throws IOException
     */
    public void test_create_text01() throws IOException {

        final String id = "test";

        final String encoding = "UTF-8";

        final String mimeType = "text/plain; charset=" + encoding;

        final String content = "Hello world!";

        DocumentImpl doc = new DocumentImpl();

        doc.setId(id);

        doc.setContentType(mimeType);

        doc.setContentEncoding(encoding);

        doc.setProperty("foo", "bar");

        doc.copyString(encoding, content);

        repo.create(doc);

        Document actual = repo.read(id);

        assertEquals("version", 0, ((RepositoryDocumentImpl) actual)
                .getVersion());

        assertEquals("Content-Type", mimeType, actual.getContentType());

        assertEquals("Content-Encoding", encoding, actual.getContentEncoding());

        assertEquals("user property", "bar", actual.getProperty("foo"));

        assertEquals("content", content, read(actual.getReader()));

    }

    /**
     * Create an empty file and verify its metadata.
     */
    public void test_create_empty() {

        final String id = "test";

        final Map<String, Object> metadata = new HashMap<String, Object>();

        metadata.put(FileMetadataSchema.ID, id);

        metadata.put("foo", "bar");

        final int version = repo.create(metadata);

        metadata.put(FileMetadataSchema.VERSION, Integer.valueOf(version));

        assertEquals("version", 0, version);

        RepositoryDocumentImpl doc = (RepositoryDocumentImpl) repo.read(id);

        assertTrue("exists", doc.exists());

        assertEquals("version", version, doc.getVersion());

        assertNotSame("versionCreateTime", 0L, doc.getVersionCreateTime());

        assertEquals("earliestVersionCreateTime", doc.getVersionCreateTime(), doc.getEarliestVersionCreateTime());

        assertEquals("metadataUpdateTime", doc.getVersionCreateTime(), doc.getMetadataUpdateTime());

        Map<String, Object> actual = doc.asMap();

        assertEquals("id", id, actual.get(FileMetadataSchema.ID));

        assertEquals("version", version, actual.get(FileMetadataSchema.VERSION));

        assertEquals("user property", "bar", actual.get("foo"));

        assertEquals("size", metadata.size(), actual.size());

    }

    /**
     * Create an empty file and write some data on it. Then update its metadata,
     * verify the new metadata and the updated version, and then write some data
     * on the new version. Verify the both file versions can be read.
     * 
     * @throws IOException
     */
    public void test_create_update() throws IOException {

        final String id = "test";

        final Map<String, Object> metadata = new HashMap<String, Object>();

        metadata.put(FileMetadataSchema.ID, id);

        metadata.put("foo", "bar");

        final int version0;
        final long createTime0;
        final byte[] expected0 = new byte[] { 1, 2, 3 };
        {

            version0 = repo.create(metadata);

            metadata.put(FileMetadataSchema.VERSION, Integer.valueOf(version0));

            assertEquals("version", 0, version0);

            RepositoryDocumentImpl doc = (RepositoryDocumentImpl) repo.read(id);

            createTime0 = doc.getVersionCreateTime();

            Map<String, Object> actual = doc.asMap();

            assertEquals("id", id, actual.get(FileMetadataSchema.ID));

            assertEquals("version", version0, actual
                    .get(FileMetadataSchema.VERSION));

            assertEquals("user property", "bar", actual.get("foo"));

            assertEquals("size", metadata.size(), actual.size());

            // write on the file version.
            repo.copyStream(id, version0, new ByteArrayInputStream(expected0));

            // verify read back.
            assertEquals("version0", expected0, read(repo.inputStream(id,
                    version0)));

        }

        // update (new file version).
        final int version1;
        final long createTime1;
        final byte[] expected1 = new byte[] { 4, 5, 6 };
        {

            // modify a user defined property.
            metadata.put("foo", "baz");

            DocumentImpl doc1 = new DocumentImpl(metadata);
            
            doc1.copyStream(expected1);
            
            version1 = repo.update( doc1 );
            
            assertEquals("version", 1, version1);

            metadata.put(FileMetadataSchema.VERSION, Integer.valueOf(version1));

            RepositoryDocumentImpl doc = (RepositoryDocumentImpl) repo.read(id);

            createTime1 = doc.getVersionCreateTime();

            assertNotSame("createTime", 0L, createTime1);

            assertNotSame("createTime", createTime0, createTime1);

            Map<String, Object> actual = doc.asMap();

            assertEquals("id", id, actual.get(FileMetadataSchema.ID));

            assertEquals("version", version1, actual
                    .get(FileMetadataSchema.VERSION));

            assertEquals("user property", "baz", actual.get("foo"));

            assertEquals("size", metadata.size(), actual.size());

//            // write on the file version.
//            repo.copyStream(id, version1, new ByteArrayInputStream(expected1));

            // verify read back.
            assertEquals("version1", expected1, read(repo.inputStream(id,
                    version1)));

            /*
             * verify read back of version0 is now an empty byte[] since that
             * version was deleted.
             */
            assertEquals("version0", new byte[]{}, read(repo.inputStream(id,
                    version0)));

            /*
             * verify that version0 is now marked as deleted.
             */
            {

                /*
                 * all metadata for the file up to (but excluding) the create
                 * time for version1.
                 */
                ITPS tps = repo.readMetadata(id, createTime1 - 1L);

                /*
                 * The version property for version0. This should have been
                 * overwritten to be deleted (a null) immediately before the new
                 * file version was created.
                 */
                ITPV tpv = tps.get(FileMetadataSchema.VERSION);

                assertEquals("version", null, tpv.getValue());

            }

        }

    }

    /**
     * Test of delete a file version verifies that the old version is marked as
     * deleted and that the data for that version are deleted as well. The test
     * also verifies that the deleted file version metadata and data remain
     * readable.
     * 
     * @todo test that the metadata and data are no longer readable after a
     *       suitable compacting merge.
     * 
     * @throws IOException
     */
    public void test_delete01() throws IOException {

        final String id = "test";

        final Map<String, Object> metadata = new HashMap<String, Object>();

        metadata.put(FileMetadataSchema.ID, id);

        metadata.put("foo", "bar");

        final int version0;
        final long createTime0;
        final byte[] expected0 = new byte[] { 1, 2, 3 };
        {

            version0 = repo.create(metadata);

            metadata.put(FileMetadataSchema.VERSION, Integer.valueOf(version0));

            assertEquals("version", 0, version0);

            RepositoryDocumentImpl doc = (RepositoryDocumentImpl) repo.read(id);

            createTime0 = doc.getVersionCreateTime();

            assertNotSame("createTime", 0L, createTime0);

            Map<String, Object> actual = doc.asMap();

            assertEquals("id", id, actual.get(FileMetadataSchema.ID));

            assertEquals("version", version0, actual
                    .get(FileMetadataSchema.VERSION));

            assertEquals("user property", "bar", actual.get("foo"));

            assertEquals("size", metadata.size(), actual.size());

            // write on the file version.
            repo.copyStream(id, version0, new ByteArrayInputStream(expected0));

            // verify read back.
            assertEquals("version0", expected0, read(repo.inputStream(id,
                    version0)));

        }

        /*
         * Delete the file version.
         * 
         * @todo test that double-delete has no effect (returns false, does (or
         * does not?) write another delete marker).
         */
        assertTrue(repo.delete(id) > 0);
        
        /*
         * Verify the file version history metadata.
         */

        ITPV[] a = repo.getAllVersionInfo(id);

        assertEquals(2, a.length);
        
        assertEquals("v0",0,((Integer)a[0].getValue()).intValue());

        assertNull("delete(v0)", a[1].getValue());

        /*
         * verify read back of version0 is now an empty byte[] since that
         * version was deleted.
         */
        assertEquals("version0", new byte[]{}, read(repo.inputStream(id,
                version0)));

        /*
         * Verify that you can still read back the file version data using a
         * historical view of the file data index whose commit time is less than
         * or equal to version1's timestamp. If it is equal then you will be
         * reading from the last state of the prior version. If it is less than
         * then you may be reading from some historical state of the prior
         * version.
         */
        {

            // the timestamp before the version was deleted.
            final long timestamp = a[1].getTimestamp();
            
            // verify read back.
            assertEquals("version0", expected0, read(repo.inputStream(id,
                    version0, TimestampUtility.asHistoricalRead(timestamp))));
            
        }
        
    }

}
